package com.jiaz.algorithm;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jiaz.algorithm.threadPool.ItemOriginThreadScoreThread;
import com.jiaz.dto.PostionScoreDto;
import com.jiaz.entity.Postion;
import com.jiaz.mapper.CheckInMapper;
import com.jiaz.mapper.PostionMapper;
import com.jiaz.pojo.ItemCheckInUser;
import com.jiaz.untils.ResultWrap;
import com.jiaz.untils.constant.VarConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author jiajiazi
 * @version 1.0
 * @date 2022/5/5 23:04
 */
@Service
@Slf4j
public class ItemAlgorithm implements RecommendAlgorithm {

    @Autowired
    private CheckInMapper checkInMapper;

    @Autowired
    private PostionMapper postionMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public ItemCheckInUser[] itemCheckInUsers = null;

    // 相邻节点权重和
    public double[] closeWeightSum = null;

    public List<Double[]>[] closeNod = null;

    /**
     * CF_item
     *
     * @return
     */
    @Override
    public double[] originAlgorithm() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 CF_Item 相似度计算");
        if (itemCheckInUsers == null) {
            itemCheckInUsers = checkInMapper.getPostion_User_CheckIn().toArray(new ItemCheckInUser[0]);
        }
        int len = itemCheckInUsers.length;
        closeWeightSum = new double[len];
        closeNod = new ArrayList[len];
        for (int i = 0; i < len; i++) {
            closeNod[i] = new ArrayList<>();
        }
        //HashMap<String, Double> itemSimilaryCache = new HashMap<>();
        for (int i = 0; i < len; i++) {
            int[] checkInUseri = itemCheckInUsers[i].getCheckInUser();
            for (int j = i; j < len; j++) {
                if (i == j) {
                    //itemSimilaryCache.put(i + ":" + j, 1.0);
                    continue;
                }
                int[] checkInUserj = itemCheckInUsers[j].getCheckInUser();
                // 分子
                int moleSum = 0;
                int ptri = 0, ptrj = 0;
                while (ptri < checkInUseri.length && ptrj < checkInUserj.length) {
                    int r = checkInUseri[ptri] - checkInUserj[ptrj];
                    if (r == 0) {
                        moleSum += 1;
                        ptrj++;
                        ptri++;
                    } else if (r > 0) {
                        ptrj++;
                    } else {
                        ptri++;
                    }
                }
                if (moleSum == 0) {
                    continue;
                }
                // 分母
                double demoSum = Math.sqrt(checkInUseri.length) * Math.sqrt(checkInUserj.length);
                if (demoSum == 0) {
                    continue;
                }
                double v = moleSum / demoSum;
                //itemSimilaryCache.put(i + ":" + j, v);
                closeWeightSum[i] += v;
                closeWeightSum[j] += v;
                closeNod[i].add(new Double[]{(double) j, v});
                closeNod[j].add(new Double[]{(double) i, v});
            }
            //批量set数据
            //redisTemplate.opsForValue().multiSet(itemSimilaryCache);
            //itemSimilaryCache.clear();
        }

        stopWatch.stop();
        log.info("【结束】 CF_Item 相似度计算，耗时：{}", stopWatch.getTotalTimeMillis());

        return null;
    }

    // int vuneuId 最终topk与之距离筛选
    @Override
    public PostionScoreDto[] originAlgorithmScore(int userId, int venueId, int topN) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 CF_Item 相似度得分计算");

        if (itemCheckInUsers == null || closeWeightSum == null || closeNod == null) {
            this.originAlgorithm();
        }
        // 与当前位置相似度最高的数量（最低为k，超过k取一半）
        int k = 20;
        Postion postion = postionMapper.selectById(venueId);
        List<Postion> postionList = postionMapper.selectList(new QueryWrapper<Postion>().select("id", "lat", "lon"));

        /**
         * 多线程版本
         */
        int num = 3;
        int n = itemCheckInUsers.length;
        //ItemOriginThreadScoreThread[] ith = new ItemOriginThreadScoreThread[num];
        //CountDownLatch countDownLatch = new CountDownLatch(num);
        //ExecutorService executorService = Executors.newFixedThreadPool(num);
        //for (int i = 0; i < num; i++) {
        //    ith[i] = new ItemOriginThreadScoreThread(redisTemplate, postionList, itemCheckInUsers, i * n / num, (i + 1) * n / num, userId, topN, countDownLatch);
        //    executorService.execute(ith[i]);
        //}
        //
        //ArrayList<PostionScoreDto> res = new ArrayList<>();
        //try {
        //    countDownLatch.await();
        //
        //    for (int i = 0; i < num; i++) {
        //        res.addAll(ith[i].postionScoreDtos);
        //    }
        //    if (res.size() < topN) {
        //        return res.toArray(new PostionScoreDto[0]);
        //    }
        //    res.sort(new Comparator<PostionScoreDto>() {
        //        @Override
        //        public int compare(PostionScoreDto o1, PostionScoreDto o2) {
        //            double d = o2.getScore() - o1.getScore();
        //            return d == 0 ? 0 : d > 0 ? 1 : -1;
        //        }
        //    });
        //    stopWatch.stop();
        //    log.info("【线程结束】 CF_Item 相似度得分计算，耗时：{}ms", stopWatch.getTotalTimeMillis());
        //    return res.subList(0, topN).toArray(new PostionScoreDto[0]);
        //} catch (InterruptedException e) {
        //    e.printStackTrace();
        //}
        //return null;

        PriorityQueue<PostionScoreDto> postionScoreDtos = new PriorityQueue<>(new Comparator<PostionScoreDto>() {
            @Override
            public int compare(PostionScoreDto o1, PostionScoreDto o2) {
                double d = o1.getScore() - o2.getScore();
                return d == 0 ? 0 : d > 0 ? 1 : -1;
            }
        });

        for (Postion postionl : postionList) {
            // 距离筛选
            if (ResultWrap.distant(postion.getLat(), postion.getLon(), postionl.getLat(), postionl.getLon()) > VarConstant.BEST_DISTANT_GEO) {
                continue;
            }
            int venueIndex = 0;
            //for (int i = 0; i < itemCheckInUsers.length; i++) {
            //    if (itemCheckInUsers[i].getVenueId() == postion.getId()) {
            //        venueIndex = i;
            //        break;
            //    }
            //}
            int beg = 0, end = n - 1;
            while (beg <= end) {
                int m = (beg + end) / 2;
                int d = itemCheckInUsers[m].getVenueId() - postionl.getId();
                if (d == 0) {
                    venueIndex = m;
                    break;
                } else if (d > 0) {
                    end = m - 1;
                } else {
                    beg = m + 1;
                }
            }
            double score = 0;
            ArrayList<Double> itemSimilary = new ArrayList<>();
            for (Double[] doubles : closeNod[venueIndex]) {
                if (itemCheckInUsers[doubles[0].intValue()].include(userId)) {
                    itemSimilary.add(doubles[1]);
                }
            }

            if (itemSimilary.size() <= k) {
                score = itemSimilary.stream().mapToDouble(Double::doubleValue).sum();
            } else {
                score = itemSimilary.stream().sorted(new Comparator<Double>() {
                    @Override
                    public int compare(Double o1, Double o2) {
                        double d = o2 - o1;
                        return d == 0 ? 0 : d > 0 ? 1 : -1;
                    }
                }).limit(k).mapToDouble(Double::doubleValue).sum();
            }
            score /= k;
            if (postionScoreDtos.size() < topN) {
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            } else if (postionScoreDtos.peek().getScore() < score) {
                postionScoreDtos.poll();
                postionScoreDtos.add(new PostionScoreDto(postionl.getId(), score));
            }
        }
        stopWatch.stop();
        log.info("【结束】 CF_Item 相似度得分计算，耗时：{}ms", stopWatch.getTotalTimeMillis());

        return postionScoreDtos.toArray(new PostionScoreDto[0]);
    }

    /**
     * venueIndex 的个性化相似度
     *
     * @param venueIndex
     * @return
     */
    @Override
    public double[] PG_gorithm(int venueIndex) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("【开始】 CF_Item_WPR 相似度得分计算");

        if (itemCheckInUsers == null || closeWeightSum == null) {
            originAlgorithm();
        }
        int n = itemCheckInUsers.length;
        double[] pgSimilaryOld = new double[n];

        double dt = 1.0E-16;
        double a = 0.85;
        int time = 0;
        pgSimilaryOld[venueIndex] = 1;
        int nthread = 2;
        ExecutorService executorService = null;
        ItemOriginThreadScoreThread[] threads = new ItemOriginThreadScoreThread[nthread];
        try {
            executorService = Executors.newFixedThreadPool(nthread);
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (true) {
            double tap = 0;
            double[] pgSimilary = new double[n];
            //for (int i = 0; i < n; i++) {
            //    double d = 0;
            //    int self = i == venueIndex ? 1 : 0;
            //    for (Double[] doubles : closeNod[i]) {
            //        int j = doubles[0].intValue();
            //        d += pgSimilaryOld[j] * doubles[1] / closeWeightSum[j];
            //    }
            //    d = (1 - a) * self / n + a * d;
            //    double abs = Math.abs(d - pgSimilaryOld[i]);
            //    if (abs > tap) {
            //        tap = abs;
            //    }
            //    pgSimilary[i] = d;
            //}
            ////////////////////////////////////////////////////////////////////////////

            CountDownLatch countDownLatch = new CountDownLatch(nthread);
            double[] taps = new double[nthread];
            for (int i = 0; i < nthread; i++) {
                threads[i] = new ItemOriginThreadScoreThread(venueIndex, n, pgSimilaryOld, pgSimilary, taps, i, closeNod, closeWeightSum, countDownLatch);
            }
            for (int i = 0; i < nthread; i++) {
                executorService.execute(threads[i]);
            }

            try {
                countDownLatch.await();
                for (double v : taps) {
                    if (v > tap) {
                        tap = v;
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //////////////////////////////////////////////////////////////////////////////
            pgSimilaryOld = pgSimilary;
            time++;
            if (tap <= dt) {
                break;
            }
            if (time % 1000 == 0) {
                log.info("CF_Item_WPR 迭代进行第{}次", time);
            }
        }
        stopWatch.stop();
        log.info("【结束】 CF_Item_WPR 相似度得分计算，耗时：{},迭代次数：{}", stopWatch.getTotalTimeMillis(), time);
        return pgSimilaryOld;
    }

    @Override
    public PostionScoreDto[] PG_gorithmScore(int userId, int venueId, int topN) {
        if (itemCheckInUsers == null || closeNod == null) {
            originAlgorithm();
        }

        PriorityQueue<PostionScoreDto> postionScoreDtos = new PriorityQueue<>(new Comparator<PostionScoreDto>() {
            @Override
            public int compare(PostionScoreDto o1, PostionScoreDto o2) {
                double d = o1.getScore() - o2.getScore();
                return d == 0 ? 0 : d > 0 ? 1 : -1;
            }
        });

        int venueIndex = 0;
        for (int i = 0; i < itemCheckInUsers.length; i++) {
            if (itemCheckInUsers[i].getVenueId() == venueId) {
                venueIndex = i;
                break;
            }
        }

        double[] itemSimilary = PG_gorithm(venueIndex);
        //List<Postion> postionList = postionMapper.selectList(new QueryWrapper<Postion>().select("id"));

        for (int i = 0; i < itemSimilary.length; i++) {
            double score = itemSimilary[i];
            if (score == 0 || i == venueIndex) {
                continue;
            }
            if (postionScoreDtos.size() < topN) {
                postionScoreDtos.add(new PostionScoreDto(itemCheckInUsers[i].getVenueId(), score));
            } else if (postionScoreDtos.peek().getScore() < score) {
                postionScoreDtos.poll();
                postionScoreDtos.add(new PostionScoreDto(itemCheckInUsers[i].getVenueId(), score));
            }
        }

        return postionScoreDtos.toArray(new PostionScoreDto[0]);
    }

}
